package com.yami.shop.service.utils;


import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.parser.ParserConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.yami.shop.common.bean.Wechat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WeChatPayHelper {

    @Autowired
    private Wechat wechat;

    private static final Map<String, Certificate> CERTIFICATE_MAP = new ConcurrentHashMap<>();

    public static CloseableHttpClient getHttpClient(Wechat wechat) throws UnsupportedEncodingException {
        // 加载商户私钥（privateKey：私钥字符串）
        PrivateKey merchantPrivateKey = PemUtil
                .loadPrivateKey(new ByteArrayInputStream(wechat.getPrivateKey().getBytes("utf-8")));
        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(wechat.getMerchantId(), new PrivateKeySigner(wechat.getMerchantSerialNumber(), merchantPrivateKey)), wechat.getApiV3Key().getBytes("UTF-8"));

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(wechat.getMerchantId(), wechat.getMerchantSerialNumber(), merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));

        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }


    /**
     * 获取微信支付的请求信息
     * @param payRequest
     * @return
     */
    public  String getPayRequest(PayRequest payRequest) {
        JSONObject reqData = new JSONObject();
        reqData.put("appid", wechat.getAppid());
        reqData.put("mchid", wechat.getMerchantId());
        // 长度max值127位
        reqData.put("description", payRequest.getOrderDesc());
        reqData.put("out_trade_no", payRequest.getPaySerialNo());

        // 失效时间，格式遵循YYYY-MM-DDTHH:mm:ss+TIMEZONE  https://tools.ietf.org/html/rfc3339
        // reqData.put("time_expire", "");
        // 将租户id放入附加信息中
        //reqData.put("attach", payRequest.getTenantId());

        String path = wechat.getNotityUrl();
        reqData.put("notify_url", path);

        Map<String, Object> amount = new HashMap<>();
        // 单位分
        amount.put("total", payRequest.getOrderAmount().multiply(new BigDecimal(100)).intValue());
        amount.put("currency", "CNY");
        reqData.put("amount", amount);

        Map<String, String> payer = new HashMap<>();
        payer.put("openid", payRequest.getOpenid());
        reqData.put("payer", payer);
        String reqBody = reqData.toString();
        return reqBody;
    }

    /**
     * JSAPI下单,返回预支付交易会话标识
     * {
     * 	"prepay_id": "wx26112221580621e9b071c00d9e093b0000"
     * }
     * @param payRequest
     * @return
     * @throws IOException
     */
    public String getJSAPIPay(PayRequest payRequest) {
        CloseableHttpClient httpClient=null;
        CloseableHttpResponse response = null;
        try {
            String reqBody = getPayRequest(payRequest);
            StringEntity entity = new StringEntity(reqBody, "UTF-8");
            HttpPost httpPost = new HttpPost(wechat.getPayUrl());
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
            httpPost.setEntity(entity);
            httpClient= getHttpClient(wechat);
            //完成签名并执行请求
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            // 处理成功
            if (statusCode == HttpStatus.SC_OK) {
                String json = EntityUtils.toString(response.getEntity());
                return json;
            }
        }catch (Exception e){

        }finally {
            try{
                httpClient.close();
                if (response != null) {
                    response.close();
                }
            }catch (Exception ee){

            }

        }
        return "";
    }

    public PayResponse getMinPay(String payJson){
        if (StringUtils.isNotBlank(payJson)) {
            JSONObject jsonObject = new JSONObject(payJson);
            String prepayId = jsonObject.getString("prepay_id");
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = RandomUtil.randomString(32);
            String packageStr = "prepay_id=" + prepayId;
            String signContent = createSign(wechat.getAppid(), timeStamp, nonceStr, packageStr);
            String privateKey = wechat.getPrivateKey().replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");
            String signStr = null;
            try {
                signStr = signStr(signContent, privateKey);
            } catch (Exception exception) {
               return null;
            }

            PayResponse res = new PayResponse();
            Map<String, String> resultMap = new HashMap<>();
            resultMap.put("timeStamp", timeStamp);
            resultMap.put("nonceStr", nonceStr);
            resultMap.put("package", packageStr);
            resultMap.put("signType", "RSA");
            resultMap.put("paySign", signStr);
            res.setRemark(prepayId);
            res.setData(resultMap);
            return res;
        }else{
            return null;
        }
    }

    public PayResponse payment(PayRequest payRequest) throws IOException {
        String jsapiPay=getJSAPIPay(payRequest);
        PayResponse res=getMinPay(jsapiPay);
        return res;
    }


    public WeChatPaymentNotifyRes validateSign(HttpServletRequest request) throws Exception {
        // 平台证书序列号
        String wechatpaySerial = request.getHeader("Wechatpay-Serial");
        // 应答时间戳
        String wechatpayTimestamp = request.getHeader("Wechatpay-Timestamp");
        // 应答随机串
        String wechatpayNonce = request.getHeader("Wechatpay-Nonce");
        // 签名
        String wechatpaySignature = request.getHeader("Wechatpay-Signature");

        String body = null;

        try (BufferedReader reader = request.getReader()) {
            body = reader.lines().collect(Collectors.joining());
        } catch (IOException e) {
            log.error("读取失败",e);
        }
        log.info("body={}",body);

        WeChatPaymentNotifyRes res=new WeChatPaymentNotifyRes();

        Certificate certificate = WeChatPayHelper.getCertificateBySerialNo(wechat,wechatpaySerial);
        // 对请求进行验签
        String signatureStr = createSign(wechatpayTimestamp, wechatpayNonce, body);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(certificate);
        signature.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        boolean verify = signature.verify(Base64Utils.decodeFromString(wechatpaySignature));
        log.info("验签结果verify={}",verify);
        if(verify){
            CallbackParams callbackParams = parseJson(body, CallbackParams.class);
            log.info("callbackParams返回:{}", ToStringBuilder.reflectionToString(callbackParams, ToStringStyle.MULTI_LINE_STYLE) + "]");
            String payInfo = WeChatPayHelper.decryptResponse(wechat.getApiV3Key(), callbackParams.getResource().getAssociatedData(),
                    callbackParams.getResource().getNonce(), callbackParams.getResource().getCiphertext());
            log.info("payInfo返回:{}", payInfo);
            WeChatTransactionData data = parseJson(payInfo, WeChatTransactionData.class);
            log.info("data返回:{}", ToStringBuilder.reflectionToString(data, ToStringStyle.MULTI_LINE_STYLE) + "]");
            if ("SUCCESS".equals(data.getTradeState())) {
                res.setValidateSign(true);
                res.setOutTradeNo(data.getOutTradeNo());
                res.setTransactionId(data.getTransactionId());
            }
        }
        return res;
    }

    private <T> T parseJson(String json, Class<T> clazz) {
        ParserConfig parserConfig = new ParserConfig();
        parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
        T obj = JSON.parseObject(json, clazz, parserConfig);

        return obj;
    }


    public static Certificate getCertificateBySerialNo(Wechat wechat,String merchantSerialNo) {
        if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechat.getMerchantSerialNumber())) {
            loadCertificate(wechat);
        }
        return CERTIFICATE_MAP.get(merchantSerialNo);
    }

    private static void loadCertificate(Wechat wechat) {

        try {
            CloseableHttpClient httpClient = getHttpClient(wechat);
            URIBuilder uriBuilder = new URIBuilder(wechat.getPaySiginUrl());
            HttpGet httpGet = new HttpGet(uriBuilder.build());
            httpGet.addHeader("Accept", "application/json");
            CloseableHttpResponse response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            // 处理成功
            if (statusCode == HttpStatus.SC_OK) {
                String json = EntityUtils.toString(response.getEntity());
                JSONObject jsonObject = new JSONObject(json);
                JSONArray arrays = jsonObject.getJSONArray("data");
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");

                if (Objects.nonNull(arrays) && arrays.length() > 0) {
                    for (int i = 0; i < arrays.length(); i++) {
                        JSONObject item = arrays.getJSONObject(i);
                        String serialNo = item.getString("serial_no");
                        JSONObject encryptCertificate = item.getJSONObject("encrypt_certificate");
                        String associatedData = encryptCertificate.getString("associated_data");
                        String nonce = encryptCertificate.getString("nonce");
                        String ciphertext = encryptCertificate.getString("ciphertext");
                        String publicKey = decryptResponse(wechat.getApiV3Key(), associatedData, nonce, ciphertext);
                        ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));

                        try {
                            Certificate certificate = certificateFactory.generateCertificate(inputStream);
                            CERTIFICATE_MAP.put(serialNo, certificate);
                        } catch (CertificateException var12) {
                        }

                    }
                }
            }

            response.close();
            httpClient.close();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return;
        } catch (Exception e) {
            e.printStackTrace();
        }

    }




    public static String decryptResponse(String apiV3Key, String associatedData, String nonce, String ciphertext) throws GeneralSecurityException, IOException {
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
        String text = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);

        return text;
    }

    private static String createSign(String... components) {
        return (String) Arrays.stream(components).collect(Collectors.joining("\n", "", "\n"));
    }

    public static String signStr(String plainText, String privateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey pk = keyFactory.generatePrivate(pkcs8KeySpec);
        return sign(plainText, pk);
    }

    public static String sign(String plainText, PrivateKey privateKey) throws Exception {
        Signature privateSignature = Signature.getInstance("SHA256withRSA");
        privateSignature.initSign(privateKey);
        privateSignature.update(plainText.getBytes(StandardCharsets.UTF_8));
        byte[] signature = privateSignature.sign();
        return Base64.getEncoder().encodeToString(signature);
    }

}
